﻿#include "mqtt_manager.h"
#include "serial_command_handler.h"
#include <SD.h>
#include <FS.h>

// ===========================================
// 静态常量定义
// ===========================================
const char* MQTTManager::MQTT_SERVER = "ka1e5a49.ala.cn-hangzhou.emqxsl.cn";
const char* MQTTManager::MQTT_USERNAME = "linyuchi";
const char* MQTTManager::MQTT_PASSWORD = "adSd@@4d";

// 播放列表文件路径
const String MQTTManager::PLAYLIST_FILE_PATH = "/playlist.json";

// 声明全局状态变量，用于智能判断绘图状态
extern bool isDrawingActive;
extern bool isDrawingPaused;

MQTTManager::MQTTManager(PatternLoader* loader, MotionController* motion, MotionController* motionForHoming, LEDManager* led) 
  : commandExecutor(nullptr)
  , patternLoader(loader)
  , mqttWiFiClient(nullptr)
  , mqttClient(nullptr)
  , mqttConnected(false)
  , lastReconnectAttempt(0)
  , connectionRetries(0)
  , connectionStartTime(0)
  , baseReconnectInterval(RECONNECT_INTERVAL)
  , maxReconnectInterval(MAX_RECONNECT_INTERVAL)
  , backoffMultiplier(BACKOFF_MULTIPLIER)
  , lastSuccessfulConnection(0)
  , consecutiveFailures(0)
  , totalConnectionAttempts(0)
  , successfulConnections(0)
  , totalMessagesSent(0)
  , totalMessagesReceived(0)
  , totalMessagesFailed(0)
  , totalBytesTransmitted(0)
  , connectionTotalTime(0)
  , lastHeartbeat(0)
  , lastStatusLog(0)
  , lastHeartbeatLog(0)
  , currentLogLevel(DEFAULT_LOG_LEVEL)
  , isDrawing(false)
  , isPaused(false)
  , currentPatternIndex(0)
  , currentPlaylist(nullptr)
{
  deviceSN = getDeviceSN();
  
  // 初始化播放列表
  currentPlaylist = new DynamicJsonDocument(2048);
  loadPlaylistFromSD();
  
  logInfo("MQTT管理器初始化完成，设备SN: " + deviceSN);
}

void MQTTManager::setCommandExecutor(CommandExecutor* executor) {
  commandExecutor = executor;
  logInfo("命令执行器已设置");
}

MQTTManager::~MQTTManager() {
  disconnect();
  
  // 清理播放列表内存
  if (currentPlaylist) {
    delete currentPlaylist;
    currentPlaylist = nullptr;
  }
}

void MQTTManager::begin() {
  logInfo("开始初始化MQTT管理器");
  
  // 加载播放列表（如果之前未加载）
  if (currentPlaylist && currentPlaylist->isNull()) {
    Serial.println("📋 开始加载播放列表...");
    if (loadPlaylistFromSD()) {
      Serial.println("✅ 播放列表加载完成");
    } else {
      Serial.println("⚠️ 播放列表加载失败，使用默认配置");
    }
  }
  
  if (WiFi.isConnected()) {
    logInfo("WiFi已连接: " + WiFi.SSID() + " (" + WiFi.localIP().toString() + ")");
    
    bool result = connect();
    if (result) {
      logInfo("MQTT初始化成功");
    } else {
      logWarn("MQTT初始化失败，将在网络任务中重试");
    }
  } else {
    logWarn("WiFi未连接，MQTT初始化延迟，状态码: " + String(WiFi.status()));
  }
}

bool MQTTManager::connect() {
  if (!WiFi.isConnected()) {
    logError("WiFi未连接，无法连接MQTT");
    return false;
  }
  
  // 初始化SSL连接
  if (!initializeSSLConnection()) {
    logError("SSL连接初始化失败");
    return false;
  }
  
  // 创建MQTT客户端
  if (mqttClient == nullptr) {
    if (!checkMemoryAvailability()) {
      logError("内存不足，无法创建MQTT客户端");
      return false;
    }
    
    mqttClient = new PubSubClient(*mqttWiFiClient);
    mqttClient->setServer(MQTT_SERVER, MQTT_PORT);
    mqttClient->setBufferSize(MQTT_BUFFER_SIZE);
    mqttClient->setSocketTimeout(CONNECTION_TIMEOUT);
    mqttClient->setKeepAlive(KEEPALIVE_INTERVAL);
    
    logInfo("MQTT客户端配置完成: 缓冲区=" + String(MQTT_BUFFER_SIZE) + "字节, 超时=" + 
            String(CONNECTION_TIMEOUT) + "秒, 心跳=" + String(KEEPALIVE_INTERVAL) + "秒");
    
    // 设置回调函数
    mqttClient->setCallback([this](char* topic, byte* payload, unsigned int length) {
      this->mqttCallback(topic, payload, length);
    });
  }
  
  if (mqttClient->connected()) {
    return true; // 已经连接
  }
  
  Serial.println("🔗 开始MQTT连接...");
  
  // 尝试域名SSL连接
  return connectWithHostname();
}

void MQTTManager::disconnect() {
  if (mqttClient != nullptr) {
    if (mqttClient->connected()) {
      // 发布设备离线状态
      publishDeviceOffline();
      delay(100); // 确保消息发送
      mqttClient->disconnect();
    }
    delete mqttClient;
    mqttClient = nullptr;
  }
  
  if (mqttWiFiClient != nullptr) {
    delete mqttWiFiClient;
    mqttWiFiClient = nullptr;
  }
  
  mqttConnected = false;
  Serial.println("🔌 MQTT连接已断开");
}

bool MQTTManager::isConnected() {
  return mqttConnected && mqttClient && mqttClient->connected();
}

void MQTTManager::loop() {
  unsigned long now = millis();
  
  // 定期记录连接状态
  if (now - lastStatusLog > STATUS_LOG_INTERVAL) {
    lastStatusLog = now;
    logConnectionStatus();
  }

  if (mqttClient == nullptr) {
    // 如果WiFi已连接但MQTT客户端未初始化，尝试初始化
    if (WiFi.isConnected()) {
      static unsigned long lastInitAttempt = 0;
      unsigned long now = millis();
      if (now - lastInitAttempt > 10000) { // 每10秒尝试一次初始化
        lastInitAttempt = now;
        Serial.println("🔄 MQTT客户端未初始化，尝试重新初始化...");
        connect(); // 这会创建客户端
      }
    }
    return;
  }
  
  // 处理MQTT消息循环
  if (mqttClient->connected()) {
    mqttClient->loop();
    mqttConnected = true;
    connectionRetries = 0; // 重置重试计数
    
    // 心跳机制 - 定期发送状态更新以维持连接
    if (now - lastHeartbeat > HEARTBEAT_INTERVAL) {
      lastHeartbeat = now;
      
      // 减少心跳日志输出频率
      if (now - lastHeartbeatLog > HEARTBEAT_LOG_INTERVAL) {
        Serial.printf("💗 发送心跳状态 (间隔: %lus)\n", HEARTBEAT_INTERVAL/1000);
        lastHeartbeatLog = now;
      }
      publishHeartbeatWithStatus();
    }
  } else {
    mqttConnected = false;
    
    // 智能重连逻辑
    unsigned long now = millis();
    if (shouldAttemptReconnect()) {
      unsigned long reconnectInterval = getNextReconnectInterval();
      lastReconnectAttempt = now;
      
      if (WiFi.isConnected()) {
        Serial.printf("🔄 MQTT重连 (第%d次, 间隔%lus)\n", 
                      connectionRetries + 1, reconnectInterval/1000);
        Serial.printf("📊 WiFi: %s, 信号=%ddBm\n", 
                      WiFi.SSID().c_str(), WiFi.RSSI());
        
        connectionStartTime = now;
        if (connect()) {
          resetConnectionRetries();
          lastSuccessfulConnection = now;
          Serial.printf("✅ MQTT重连成功 (耗时%lums)\n", millis() - connectionStartTime);
        } else {
          handleConnectionFailure();
        }
      } else {
        Serial.println("❌ WiFi未连接，重置MQTT重连状态");
        resetConnectionRetries();
      }
    }
  }
}

void MQTTManager::mqttCallback(char* topic, byte* payload, unsigned int length) {
  totalMessagesReceived++;
  
  // 检查消息长度限制，避免内存溢出
  if (length > MQTT_BUFFER_SIZE) {
    logError("消息过大: " + String(length) + "字节，超过限制" + String(MQTT_BUFFER_SIZE) + "字节");
    publishError("Message too large");
    return;
  }
  
  // 检查内存可用性
  if (!checkMemoryAvailability()) {
    logWarn("内存不足，跳过消息处理");
    return;
  }
  
  // 安全地构建消息字符串，预分配内存
  String message;
  message.reserve(length + 1);
  for (unsigned int i = 0; i < length; i++) {
    message += (char)payload[i];
  }
  
  String topicStr = String(topic);
  
  // 减少调试输出频率
  static unsigned long lastMsgLog = 0;
  unsigned long now = millis();
  if (now - lastMsgLog > 10000) { // 每10秒最多打印一次
    Serial.printf("📨 MQTT消息: %s, %d字节\n", topicStr.c_str(), length);
    lastMsgLog = now;
  }
  
  // 解析主题，获取命令类别
  String category = "";
  int lastSlash = topicStr.lastIndexOf('/');
  if (lastSlash >= 0 && lastSlash < topicStr.length() - 1) {
    category = topicStr.substring(lastSlash + 1);
  }
  
  if (category == "command") {
    // 使用静态JSON文档避免动态分配
    StaticJsonDocument<JSON_BUFFER_SIZE> doc;
    if (!parseStaticJson(message, doc)) {
      publishError("Command JSON parse failed");
      return;
    }
    
    // 支持两种格式：{"cmd": "start"} 和 {"message": "start"}
    String command = "";
    if (doc.containsKey("cmd")) {
      command = doc["cmd"].as<String>();
    } else if (doc.containsKey("message")) {
      command = doc["message"].as<String>();
    }
    
    if (command.length() > 0) {
      Serial.printf("🎯 执行命令: %s\n", command.c_str());
      handleCommand(command, doc);
    } else {
      Serial.println("❌ 缺少命令字段");
      publishError("Missing command field");
    }
    
  } else if (category == "heartbeat") {
    // 处理心跳请求
    StaticJsonDocument<HEARTBEAT_JSON_SIZE> doc; // 🔧 修复：使用专用心跳缓冲区大小(800字节)
    if (parseStaticJson(message, doc)) {
      String requestType = doc["request"] | "";
      if (requestType == "status" || requestType == "heartbeat") {
        publishHeartbeatWithStatus();
      }
    }
  }
}

void MQTTManager::subscribeToTopics() {
  if (!mqttClient || !mqttClient->connected()) {
    return;
  }
  
  // 订阅命令主题
  String commandTopic = buildTopicPath("command");
  if (mqttClient->subscribe(commandTopic.c_str())) {
    Serial.println("✅ 订阅命令主题: " + commandTopic);
  } else {
    Serial.println("❌ 订阅命令主题失败: " + commandTopic);
  }
  
  // 订阅心跳主题（用于接收状态查询等命令）
  String heartbeatTopic = buildTopicPath("heartbeat");
  if (mqttClient->subscribe(heartbeatTopic.c_str())) {
    Serial.println("✅ 订阅心跳主题: " + heartbeatTopic);
  } else {
    Serial.println("❌ 订阅心跳主题失败: " + heartbeatTopic);
  }
}

void MQTTManager::handleCommand(const String& command, const DynamicJsonDocument& payload) {
  if (!commandExecutor) {
    Serial.println("❌ CommandExecutor未设置");
    publishError("System not ready");
    return;
  }
  
  // 根据命令类型分发到不同的处理方法
  if (command == "start" || command == "play" || command == "pause" || 
      command == "stop" || command == "next" || command == "prev" || command == "home" ||
      command == "sweep" || command == "sweepprep" || command == "sweepprev" || command == "sweepnext") {
    handleBasicCommands(command, payload);
  } else if (command == "setspeed" || command == "setaccel" || command == "getspeed" || 
             command == "compensation" || command == "rotsteps" || command == "radsteps") {
    handleMotionCommands(command, payload);
  } else if (command == "ledon" || command == "ledoff" || command == "brightness" || 
             command == "draweffect" || command == "drawcolor" || command == "drawbright" || 
             command == "drawspeed" || command == "drawstatus" || command == "light") {
    handleLEDCommands(command, payload);
  } else if (command == "autoplay" || command == "autoplaytime" || command == "autoplaymode") {
    handleAutoPlayCommands(command, payload);
  } else if (command == "autoresume" || command == "autoresumetime") {
    handleAutoResumeCommands(command, payload);
  } else if (command == "playlist") {
    // 🔧 新增：播放列表管理命令
    handlePlaylistCommands(command, payload);
  } else if (command == "download") {
    // 🔧 新增：THR文件下载命令
    handleDownloadCommands(command, payload);
  } else if (command == "status" || command == "info") {
    handleSystemCommands(command, payload);
  } else {
    Serial.printf("❓ 未知命令: %s\n", command.c_str());
    publishError("Unknown command: " + command);
  }
}

void MQTTManager::handleBasicCommands(const String& command, const DynamicJsonDocument& payload) {
  int result = 0;
  
  if (command == "start" || command == "play") {
    // 检查是否指定了图案文件
    if (payload.containsKey("pattern")) {
      String patternName = payload["pattern"].as<String>();
      Serial.printf("🎨 加载图案: %s\n", patternName.c_str());
      result = commandExecutor->executeLoadCommand(patternName);
      if (result != 0) {
        publishError("Failed to load pattern: " + patternName);
        return;
      }
    }
    result = commandExecutor->executeStartCommand();
    if (result == 0) {
      publishStatus("playing", "Started");
    } else {
      publishError("Start failed: " + String(result));
    }
    
  } else if (command == "pause") {
    result = commandExecutor->executePauseCommand();
    publishStatus(result == 0 ? "paused" : "error", result == 0 ? "Paused" : "Pause failed");
    
  } else if (command == "stop") {
    result = commandExecutor->executeStopCommand();
    publishStatus(result == 0 ? "stopped" : "error", result == 0 ? "Stopped" : "Stop failed");
    
  } else if (command == "next") {
    Serial.println("🔄 切换下一个图案");
    result = commandExecutor->executeNextCommand();
    if (result == 0) {
      int startResult = commandExecutor->executeStartCommand();
      if (startResult == 0) {
        publishStatus("playing", "Next pattern started");
      } else {
        publishError("Next loaded but start failed: " + String(startResult));
      }
    } else {
      publishError("Next failed: " + String(result));
    }
    
  } else if (command == "prev") {
    Serial.println("🔄 切换上一个图案");
    result = commandExecutor->executePrevCommand();
    if (result == 0) {
      int startResult = commandExecutor->executeStartCommand();
      if (startResult == 0) {
        publishStatus("playing", "Previous pattern started");
      } else {
        publishError("Previous loaded but start failed: " + String(startResult));
      }
    } else {
      publishError("Previous failed: " + String(result));
    }
    
  } else if (command == "home") {
    result = commandExecutor->executeHomeCommand();
    publishStatus(result == 0 ? "homed" : "error", result == 0 ? "Homed" : "Home failed");
    
  } else if (command == "sweep") {
    Serial.println("🧹 执行扫图命令");
    result = commandExecutor->executeSweepCommand();
    if (result == 0) {
      publishStatus("swept", "Sand surface cleaned");
    } else {
      publishError("Sweep failed: " + String(result));
    }
    
  } else if (command == "sweepprep") {
    Serial.println("🧹🏠 执行扫图准备流程");
    result = commandExecutor->executeSweepAndPrepareCommand();
    if (result == 0) {
      publishStatus("prepared", "Sweep completed, ready to draw");
    } else {
      publishError("Sweep prepare failed: " + String(result));
    }
    
  } else if (command == "sweepprev") {
    Serial.println("🧹🏠⬅️ 执行扫图准备流程（切换到上一个图案）");
    result = commandExecutor->executeSweepPrevCommand();
    if (result == 0) {
      publishStatus("prepared", "Sweep completed, switched to previous pattern and ready to draw");
    } else {
      publishError("Sweep prepare with previous pattern failed: " + String(result));
    }
    
  } else if (command == "sweepnext") {
    Serial.println("🧹🏠➡️ 执行扫图准备流程（切换到下一个图案）");
    result = commandExecutor->executeSweepNextCommand();
    if (result == 0) {
      publishStatus("prepared", "Sweep completed, switched to next pattern and ready to draw");
    } else {
      publishError("Sweep prepare with next pattern failed: " + String(result));
    }
  }
}

void MQTTManager::handleMotionCommands(const String& command, const DynamicJsonDocument& payload) {
  int result = 0;
  
  if (command == "setspeed") {
    if (payload.containsKey("value")) {
      float speed = payload["value"].as<float>();
      result = commandExecutor->setDrawingSpeed(speed);
      publishStatus(result == 0 ? "speed_set" : "error", 
                   result == 0 ? "Speed: " + String(speed) : "Speed set failed");
    } else {
      publishError("setspeed requires 'value' parameter");
    }
    
  } else if (command == "setaccel") {
    if (payload.containsKey("value")) {
      float accel = payload["value"].as<float>();
      result = commandExecutor->setAcceleration(accel);
      publishStatus(result == 0 ? "accel_set" : "error",
                   result == 0 ? "Accel: " + String(accel) : "Accel set failed");
    } else {
      publishError("setaccel requires 'value' parameter");
    }
    
  } else if (command == "getspeed") {
    String motionInfo = commandExecutor->getMotionParametersJson();
    publishMessage("response", motionInfo);
    
  } else if (command == "compensation") {
    if (payload.containsKey("value")) {
      float comp = payload["value"].as<float>();
      if (comp >= 0.0 && comp <= 1.0) {
        result = commandExecutor->setCompensationFactor(comp);
        publishStatus(result == 0 ? "compensation_set" : "error",
                     result == 0 ? "Compensation: " + String(comp) : "Compensation failed");
      } else {
        publishError("Compensation must be 0.0-1.0");
      }
    } else {
      publishError("compensation requires 'value' parameter");
    }
    
  } else if (command == "rotsteps") {
    if (payload.containsKey("value")) {
      long steps = payload["value"].as<long>();
      if (steps > 0 && steps <= 1000000) {
        result = commandExecutor->setRotationStepsPerRev(steps);
        publishStatus(result == 0 ? "rotsteps_set" : "error",
                     result == 0 ? "RotSteps: " + String(steps) : "RotSteps failed");
      } else {
        publishError("Rotation steps must be 1-1000000");
      }
    } else {
      publishError("rotsteps requires 'value' parameter");
    }
    
  } else if (command == "radsteps") {
    if (payload.containsKey("value")) {
      long steps = payload["value"].as<long>();
      if (steps > 0 && steps <= 500000) {
        result = commandExecutor->setRadiusMaxSteps(steps);
        publishStatus(result == 0 ? "radsteps_set" : "error",
                     result == 0 ? "RadSteps: " + String(steps) : "RadSteps failed");
      } else {
        publishError("Radius steps must be 1-500000");
      }
    } else {
      publishError("radsteps requires 'value' parameter");
    }
  }
}

void MQTTManager::handleLEDCommands(const String& command, const DynamicJsonDocument& payload) {
  int result = 0;
  
  if (command == "ledon") {
    result = commandExecutor->enableLED(true);
    publishStatus(result == 0 ? "led_enabled" : "error", 
                 result == 0 ? "LED ON" : "LED enable failed");
    
  } else if (command == "ledoff") {
    result = commandExecutor->enableLED(false);
    publishStatus(result == 0 ? "led_disabled" : "error",
                 result == 0 ? "LED OFF" : "LED disable failed");
    
  } else if (command == "brightness") {
    if (payload.containsKey("value")) {
      int brightness = payload["value"].as<int>();
      if (brightness >= 0 && brightness <= 255) {
        result = commandExecutor->setGlobalBrightness(brightness);
        publishStatus(result == 0 ? "brightness_set" : "error",
                     result == 0 ? "Brightness: " + String(brightness) : "Brightness failed");
      } else {
        publishError("Brightness must be 0-255");
      }
    } else {
      publishError("brightness requires 'value' parameter");
    }
    
  } else if (command == "draweffect") {
    if (payload.containsKey("value")) {
      int effectId = payload["value"].as<int>();
      if (effectId >= 0 && effectId <= 10) {
        result = commandExecutor->setDrawingEffect(effectId);
        publishStatus(result == 0 ? "draweffect_set" : "error",
                     result == 0 ? "Effect: " + String(effectId) : "Effect failed");
      } else {
        publishError("Drawing effect must be 0-10");
      }
    } else {
      publishError("draweffect requires 'value' parameter");
    }
    
  } else if (command == "drawcolor") {
    if (payload.containsKey("r") && payload.containsKey("g") && payload.containsKey("b")) {
      int r = payload["r"].as<int>();
      int g = payload["g"].as<int>();
      int b = payload["b"].as<int>();
      if (r >= 0 && r <= 255 && g >= 0 && g <= 255 && b >= 0 && b <= 255) {
        result = commandExecutor->setDrawingColor(r, g, b);
        publishStatus(result == 0 ? "drawcolor_set" : "error",
                     result == 0 ? "RGB(" + String(r) + "," + String(g) + "," + String(b) + ")" : "Color failed");
      } else {
        publishError("RGB values must be 0-255");
      }
    } else {
      publishError("drawcolor requires 'r', 'g', 'b' parameters");
    }
    
  } else if (command == "drawbright") {
    if (payload.containsKey("value")) {
      int brightness = payload["value"].as<int>();
      if (brightness >= 0 && brightness <= 255) {
        result = commandExecutor->setDrawingBrightness(brightness);
        publishStatus(result == 0 ? "drawbright_set" : "error",
                     result == 0 ? "DrawBright: " + String(brightness) : "DrawBright failed");
      } else {
        publishError("Drawing brightness must be 0-255");
      }
    } else {
      publishError("drawbright requires 'value' parameter");
    }
    
  } else if (command == "drawspeed") {
    if (payload.containsKey("value")) {
      int speed = payload["value"].as<int>();
      if (speed >= 1 && speed <= 10) {
        result = commandExecutor->setDrawingSpeed(speed);
        publishStatus(result == 0 ? "drawspeed_set" : "error",
                     result == 0 ? "DrawSpeed: " + String(speed) : "DrawSpeed failed");
      } else {
        publishError("Drawing speed must be 1-10");
      }
    } else {
      publishError("drawspeed requires 'value' parameter");
    }
    
  } else if (command == "drawstatus") {
    String statusResult = commandExecutor->getLEDStatus();
    publishMessage("response", statusResult);
  }
}

void MQTTManager::handleSystemCommands(const String& command, const DynamicJsonDocument& payload) {
  if (command == "status") {
    String statusResult = commandExecutor->executeStatusCommand();
    publishMessage("response", statusResult);
    
  }
}

void MQTTManager::handleAutoPlayCommands(const String& command, const DynamicJsonDocument& payload) {
  int result = 0;
  
  if (command == "autoplay") {
    // 支持多种参数格式：{"cmd": "autoplay", "param": "on"} 或 {"cmd": "autoplay on"}
    String param = "";
    if (payload.containsKey("param")) {
      param = payload["param"].as<String>();
    } else if (payload.containsKey("state")) {
      param = payload["state"].as<String>();
    } else {
      // 从命令本身解析参数，如 "autoplay on"
      String fullCmd = payload["cmd"].as<String>();
      int spaceIndex = fullCmd.indexOf(' ');
      if (spaceIndex > 0) {
        param = fullCmd.substring(spaceIndex + 1);
      }
    }
    
    param.toLowerCase();
    param.trim();
    
    if (param == "on" || param == "true" || param == "1" || param == "enable") {
      result = commandExecutor->executeAutoPlayEnableCommand(true);
      if (result == 0) {
        publishStatus("autoplay_enabled", "AutoPlay enabled");
      } else {
        publishError("AutoPlay enable failed: " + String(result));
      }
    } else if (param == "off" || param == "false" || param == "0" || param == "disable") {
      result = commandExecutor->executeAutoPlayEnableCommand(false);
      if (result == 0) {
        publishStatus("autoplay_disabled", "AutoPlay disabled");
      } else {
        publishError("AutoPlay disable failed: " + String(result));
      }
    } else if (param == "status" || param == "") {
      result = commandExecutor->executeAutoPlayStatusCommand();
      // 状态命令通过内部处理发送响应，这里不需要额外发布
    } else {
      publishError("Invalid autoplay parameter: " + param + ". Use on/off/status");
    }
    
  } else if (command == "autoplaytime") {
    int minutes = 0;
    if (payload.containsKey("minutes")) {
      minutes = payload["minutes"].as<int>();
    } else if (payload.containsKey("time")) {
      minutes = payload["time"].as<int>();
    } else if (payload.containsKey("param")) {
      minutes = payload["param"].as<String>().toInt();
    }
    
    if (minutes >= 1 && minutes <= 60) {
      result = commandExecutor->executeAutoPlayTimeCommand(minutes);
      if (result == 0) {
        publishStatus("autoplay_time_set", "AutoPlay time set to " + String(minutes) + " minutes");
      } else {
        publishError("AutoPlay time set failed: " + String(result));
      }
    } else {
      publishError("Invalid autoplay time: " + String(minutes) + ". Range: 1-60 minutes");
    }
    
  } else if (command == "autoplaymode") {
    String mode = "";
    if (payload.containsKey("mode")) {
      mode = payload["mode"].as<String>();
    } else if (payload.containsKey("param")) {
      mode = payload["param"].as<String>();
    }
    
    mode.toLowerCase();
    mode.trim();
    
    if (mode == "sweep" || mode == "normal") {
      bool useSweepMode = (mode == "sweep");
      result = commandExecutor->executeAutoPlayModeCommand(useSweepMode);
      if (result == 0) {
        publishStatus("autoplay_mode_set", "AutoPlay mode set to " + mode);
      } else {
        publishError("AutoPlay mode set failed: " + String(result));
      }
    } else {
      publishError("Invalid autoplay mode: " + mode + ". Use sweep/normal");
    }
  }
}

void MQTTManager::handleAutoResumeCommands(const String& command, const DynamicJsonDocument& payload) {
  int result = 0;
  
  if (command == "autoresume") {
    // 支持多种参数格式：{"cmd": "autoresume", "param": "on"} 或 {"cmd": "autoresume on"}
    String param;
    
    if (payload.containsKey("param")) {
      param = payload["param"].as<String>();
    } else {
      // 从命令本身解析参数，如 "autoresume on"
      int spaceIndex = command.indexOf(' ');
      if (spaceIndex > 0) {
        param = command.substring(spaceIndex + 1);
      }
    }
    
    param.trim();
    param.toLowerCase();
    
    if (param == "on") {
      result = commandExecutor->executeAutoResumeEnableCommand();
      if (result == 0) {
        publishStatus("autoresume_enabled", "AutoResume enabled (60s delay)");
      } else {
        publishError("AutoResume enable failed: " + String(result));
      }
    } else if (param == "off") {
      commandExecutor->setAutoResumeConfig(false, 60);
      publishStatus("autoresume_disabled", "AutoResume disabled");
    } else if (param == "status") {
      result = commandExecutor->executeAutoResumeStatusCommand();
    } else {
      publishError("Invalid autoresume parameter: " + param + ". Use on/off/status");
    }
    
  } else if (command == "autoresumetime") {
    String timeStr;
    
    if (payload.containsKey("param")) {
      timeStr = payload["param"].as<String>();
    } else if (payload.containsKey("time")) {
      timeStr = payload["time"].as<String>();
    }
    
    if (timeStr.length() > 0) {
      int seconds = timeStr.toInt();
      if (seconds >= 5 && seconds <= 600) { // 5秒到10分钟
        commandExecutor->setAutoResumeConfig(true, seconds);
        publishStatus("autoresume_time_set", "AutoResume time set to " + String(seconds) + " seconds");
      } else {
        publishError("Invalid autoresume time: " + timeStr + ". Range: 5-600 seconds");
      }
    } else {
      publishError("Missing autoresume time parameter");
    }
  }
}

String MQTTManager::buildTopicPath(const String& category) {
  // 主题命名规范：/SANDART001/${SN}/${category}
  return "/SANDART001/" + deviceSN + "/" + category;
}

String MQTTManager::getDeviceSN() {
  // 使用ESP32的芯片ID作为设备序列号
  uint64_t chipid = ESP.getEfuseMac();
  return String((uint32_t)(chipid >> 32), HEX) + String((uint32_t)chipid, HEX);
}

void MQTTManager::publishMessage(const String& category, const String& message) {
  if (!mqttClient) {
    Serial.println("❌ MQTT客户端未创建，无法发布消息");
    return;
  }
  
  // 内存检查 - 确保有足够内存处理消息
  if (!checkMemoryAvailability()) {
    Serial.printf("⚠️ 内存不足，跳过消息发布: 可用=%dKB\n", ESP.getFreeHeap() / 1024);
    return;
  }
  
  // 检查消息大小限制
  if (message.length() > MQTT_BUFFER_SIZE) {
    Serial.printf("⚠️ 消息过大: %d字节，超过%d字节限制\n", message.length(), MQTT_BUFFER_SIZE);
    return;
  }
  
  // 检查连接状态并尝试重连
  if (!mqttClient->connected()) {
    Serial.println("❌ MQTT未连接，尝试重连后发布消息");
    
    // 检查WiFi连接
    if (!WiFi.isConnected()) {
      Serial.println("⚠️ WiFi未连接，跳过MQTT重连");
      return;
    }
    
    // 多次尝试MQTT重连
    bool reconnected = false;
    for (int i = 0; i < 3; i++) {
      Serial.printf("🔄 MQTT重连尝试 %d/3...\n", i + 1);
      
      if (connect()) {
        reconnected = true;
        Serial.printf("✅ MQTT重连成功 (尝试 %d/3)\n", i + 1);
        break;
      } else {
        Serial.printf("❌ MQTT重连失败 (尝试 %d/3)\n", i + 1);
        if (i < 2) { // 不是最后一次尝试
          delay(1000 * (i + 1)); // 递增延迟
        }
      }
    }
    
    if (!reconnected) {
      Serial.println("❌ MQTT重连最终失败，消息发布取消");
      return;
    }
  }
  
  String topic = buildTopicPath(category);
  
  // 减少调试输出频率，避免串口缓冲区溢出
  static unsigned long lastPublishLog = 0;
  unsigned long now = millis();
  if (now - lastPublishLog > 30000) { // 每30秒最多打印一次发布信息
    Serial.printf("📤 发布MQTT消息: %s, %d字节\n", topic.c_str(), message.length());
    lastPublishLog = now;
  }
  
  bool success = mqttClient->publish(topic.c_str(), message.c_str());
  
  if (success) {
    totalMessagesSent++;
    totalBytesTransmitted += message.length();
    logDebug("MQTT消息发布成功: " + topic);
  } else {
    totalMessagesFailed++;
    logError("MQTT发布失败: " + topic + ", 状态=" + String(mqttClient->state()));
    printMQTTError(mqttClient->state());
    mqttConnected = false; // 标记连接失败
  }
}

void MQTTManager::publishStatus(const String& status, const String& message) {
  // 使用固定大小的JSON文档以节省内存
  StaticJsonDocument<HEARTBEAT_JSON_SIZE> statusDoc;
  
  // 基础心跳信息（使用短字段名节省内存）
  statusDoc["hb"] = true;
  statusDoc["ts"] = millis();
  statusDoc["sn"] = deviceSN.c_str(); // 避免字符串复制
  
  // 状态信息
  statusDoc["st"] = status.c_str();
  if (message.length() > 0 && message.length() < 40) { // 限制消息长度以节省内存
    statusDoc["msg"] = message.c_str();
  }
  statusDoc["on"] = true;
  
  // 系统信息（仅关键信息）
  statusDoc["heap"] = ESP.getFreeHeap() / 1024; // 以KB为单位节省空间
  statusDoc["rssi"] = WiFi.RSSI();
  
  // 设备状态
  if (commandExecutor) {
    statusDoc["draw"] = isDrawingActive;
    statusDoc["pause"] = isDrawingPaused;
    statusDoc["pat"] = currentPatternIndex;
  }
  
  // 使用预分配的字符串缓冲区
  String statusMessage;
  statusMessage.reserve(HEARTBEAT_JSON_SIZE);
  serializeJson(statusDoc, statusMessage);
  
  // 减少状态日志输出频率
  static unsigned long lastStatusLog = 0;
  unsigned long now = millis();
  if (now - lastStatusLog > 60000) { // 每分钟最多打印一次
    Serial.printf("📊 状态消息: %d字节\n", statusMessage.length());
    lastStatusLog = now;
  }
  
  publishMessage("heartbeat", statusMessage);
}

void MQTTManager::publishError(const String& error) {
  DynamicJsonDocument errorDoc(512);
  errorDoc["error"] = error;
  errorDoc["timestamp"] = millis();
  errorDoc["device_sn"] = deviceSN;
  
  String errorMessage;
  serializeJson(errorDoc, errorMessage);
  
  publishMessage("error", errorMessage);
}

void MQTTManager::publishDeviceOnline() {
  // 使用心跳机制发送上线状态
  publishHeartbeatWithStatus();
  Serial.println("📡 设备上线状态已通过心跳发送");
}

void MQTTManager::publishDeviceOffline() {
  DynamicJsonDocument offlineDoc(256);
  offlineDoc["online"] = false;
  offlineDoc["timestamp"] = millis();
  offlineDoc["device_sn"] = deviceSN;
  offlineDoc["heartbeat"] = false; // 表示离线
  offlineDoc["status"] = "offline";
  
  String offlineMessage;
  serializeJson(offlineDoc, offlineMessage);
  
  // 离线消息仍然使用心跳主题
  publishMessage("heartbeat", offlineMessage);
}

void MQTTManager::setDrawingState(bool drawing, bool paused) {
  isDrawing = drawing;
  isPaused = paused;
}

void MQTTManager::setCurrentPatternIndex(int index) {
  currentPatternIndex = index;
}

void MQTTManager::printStatus() {
  Serial.println("=== MQTT管理器状态 ===");
  Serial.printf("🔗 连接状态: %s\n", isConnected() ? "✅已连接" : "❌未连接");
  Serial.println("📡 服务器: " + String(MQTT_SERVER) + ":" + String(MQTT_PORT));
  Serial.println("👤 用户名: " + String(MQTT_USERNAME));
  Serial.println("🆔 设备SN: " + deviceSN);
  Serial.println("📝 订阅主题: " + buildTopicPath("command"));
  Serial.printf("🎨 绘图状态: %s\n", isDrawing ? (isPaused ? "暂停" : "进行中") : "停止");
  Serial.printf("📊 当前图案索引: %d\n", currentPatternIndex);
  Serial.println("===================");
}

// 辅助连接方法 - 使用域名连接
bool MQTTManager::connectWithHostname() {
  totalConnectionAttempts++;
  unsigned long connectionStart = millis();
  
  logInfo("开始SSL连接: " + String(MQTT_SERVER) + ":" + String(MQTT_PORT));
  
  // DNS解析检查
  IPAddress serverIP;
  unsigned long dnsStart = millis();
  
  if (!WiFi.hostByName(MQTT_SERVER, serverIP)) {
    logError("DNS解析失败: " + String(MQTT_SERVER));
    return false;
  }
  
  unsigned long dnsTime = millis() - dnsStart;
  logDebug("DNS解析成功: " + serverIP.toString() + " (" + String(dnsTime) + "ms)");
  
  // SSL连接验证（带超时检查）
  unsigned long sslStart = millis();
  logDebug("建立SSL连接...");
  
  if (!mqttWiFiClient->connect(MQTT_SERVER, MQTT_PORT)) {
    unsigned long sslTime = millis() - sslStart;
    logError("SSL连接失败 (耗时" + String(sslTime) + "ms)");
    return false;
  }
  
  unsigned long sslTime = millis() - sslStart;
  logDebug("SSL连接建立成功 (" + String(sslTime) + "ms)");
  
  // 生成唯一客户端ID（包含时间戳避免冲突）
  String clientId = "SANDART001_" + deviceSN + "_" + String(millis() % 10000);
  
  // 创建遗嘱消息
  StaticJsonDocument<128> willDoc;
  willDoc["online"] = false;
  willDoc["reason"] = "disconnect";
  willDoc["ts"] = millis();
  
  String willMessage;
  serializeJson(willDoc, willMessage);
  String willTopic = buildTopicPath("heartbeat");
  
  // MQTT连接尝试（带超时监控）
  unsigned long mqttStart = millis();
  logDebug("MQTT认证: " + clientId);
  
  bool connected = mqttClient->connect(
    clientId.c_str(), 
    MQTT_USERNAME, 
    MQTT_PASSWORD,
    willTopic.c_str(), 
    0, 
    true, 
    willMessage.c_str()
  );
  
  unsigned long mqttTime = millis() - mqttStart;
  unsigned long totalConnectionTime = millis() - connectionStart;
  
  if (connected) {
    successfulConnections++;
    connectionTotalTime += totalConnectionTime;
    mqttConnected = true;
    
    logInfo("MQTT连接成功 (MQTT:" + String(mqttTime) + "ms, 总计:" + String(totalConnectionTime) + "ms)");
    
    // 订阅主题
    subscribeToTopics();
    
    // 延迟发送上线状态，确保连接稳定
    delay(100);
    publishDeviceOnline();
    
    return true;
  } else {
    int errorCode = mqttClient->state();
    logError("MQTT连接失败: 错误=" + String(errorCode) + ", 耗时=" + String(totalConnectionTime) + "ms");
    printMQTTError(errorCode);
    
    // 关闭SSL连接以释放资源
    mqttWiFiClient->stop();
    return false;
  }
}

// 打印MQTT错误信息
void MQTTManager::printMQTTError(int errorCode) {
  Serial.printf("错误代码: %d - ", errorCode);
  
  switch (errorCode) {
    case -4:
      Serial.println("连接超时");
      Serial.println("💡 建议: 检查网络连接和服务器地址");
      break;
    case -3:
      Serial.println("连接丢失");
      Serial.println("💡 建议: 检查网络稳定性");
      break;
    case -2:
      Serial.println("连接失败");
      Serial.println("💡 建议: 检查服务器是否可达，端口是否正确");
      break;
    case -1:
      Serial.println("连接断开");
      Serial.println("💡 建议: 检查网络连接");
      break;
    case 1:
      Serial.println("协议版本错误");
      Serial.println("💡 建议: 检查MQTT协议版本");
      break;
    case 2:
      Serial.println("客户端ID被拒绝");
      Serial.println("💡 建议: 更改客户端ID或检查服务器设置");
      break;
    case 3:
      Serial.println("服务器不可用");
      Serial.println("💡 建议: 稍后重试或联系服务器管理员");
      break;
    case 4:
      Serial.println("用户名或密码错误");
      Serial.println("💡 建议: 检查用户名和密码是否正确");
      break;
    case 5:
      Serial.println("未授权");
      Serial.println("💡 建议: 检查账户权限");
      break;
    default:
      Serial.printf("未知错误 (%d)\n", errorCode);
      Serial.println("💡 建议: 查看PubSubClient文档");
      break;
  }
}

// 发送包含完整状态的心跳消息
void MQTTManager::publishHeartbeatWithStatus() {
  // 使用静态JSON文档以避免动态内存分配
  StaticJsonDocument<HEARTBEAT_JSON_SIZE> heartbeatDoc;
  
  // 基础心跳信息
  heartbeatDoc["hb"] = true;
  heartbeatDoc["ts"] = millis() / 1000; // 转换为秒
  heartbeatDoc["sn"] = deviceSN.c_str();
  heartbeatDoc["on"] = true;
  heartbeatDoc["heap"] = ESP.getFreeHeap() / 1024; // KB单位
  heartbeatDoc["rssi"] = WiFi.RSSI();
  heartbeatDoc["ver"] = "2.4-OPT"; // 版本号
  heartbeatDoc["temperature"] = 45; // 温度（可以从实际传感器读取）
  
  // 设备状态：0=idle, 1=drawing, 2=paused, 3=error
  int deviceState = 0;
  if (commandExecutor) {
    if (isDrawingActive) {
      deviceState = isDrawingPaused ? 2 : 1;
    }
  }
  heartbeatDoc["state"] = deviceState;
  
  // Pattern 信息
  JsonObject pattern = heartbeatDoc.createNestedObject("pattern");
  if (commandExecutor) {
    // 模拟pattern_id（可以根据实际需要修改）
    pattern["pattern_id"] = "68cd112eeef9cb5e0d1078bb";
    
    // 计算进度（基于当前绘图状态）
    float progress = 0.0;
    int duration = 180; // 默认预估时长（秒）
    int elapsed_time = 0;
    
    // 如果正在绘图，计算实际进度
    if (isDrawingActive && patternLoader) {
      // 这里需要从PatternLoader获取实际进度
      // 暂时使用模拟数据
      progress = 0.35; // 35%进度
      elapsed_time = 63; // 已经过时间
    }
    
    pattern["progress"] = progress;
    pattern["status"] = isDrawingActive ? (isDrawingPaused ? 2 : 1) : 0; // 0=停止, 1=运行, 2=暂停
    pattern["duration"] = duration;
    pattern["elapsed_time"] = elapsed_time;
    pattern["remaining_time"] = duration - elapsed_time;
    pattern["started_at"] = 1758360000; // Unix时间戳（可以记录实际开始时间）
    pattern["estimated_end"] = 1758360000 + duration;
  } else {
    pattern["pattern_id"] = "";
    pattern["progress"] = 0.0;
    pattern["status"] = 0;
    pattern["duration"] = 0;
    pattern["elapsed_time"] = 0;
    pattern["remaining_time"] = 0;
    pattern["started_at"] = 0;
    pattern["estimated_end"] = 0;
  }
  
  // Playback 信息
  JsonObject playback = heartbeatDoc.createNestedObject("playback");
  playback["speed"] = 100; // 播放速度百分比
  playback["is_paused"] = isDrawingPaused;
  if (isDrawingPaused) {
    playback["pause_reason"] = "用户暂停";
  } else {
    playback["pause_reason"] = nullptr;
  }
  playback["auto_clear"] = true; // 自动清理
  playback["loop_mode"] = "single"; // 循环模式：single, repeat, loop
  playback["pause_after_complete"] = false; // 完成后是否暂停
  playback["pause_duration"] = 300; // 暂停时长（秒）
  playback["clear_before_new"] = true; // 新图案前是否清理
  
  // Light Settings 信息
  JsonObject lightSettings = heartbeatDoc.createNestedObject("lightSettings");
  lightSettings["enabled"] = true; // LED是否启用
  lightSettings["mode"] = "constant"; // 模式：constant, breathing, rainbow等
  lightSettings["color"] = "#FF0000"; // 颜色（十六进制）
  lightSettings["brightness"] = 80; // 亮度百分比
  lightSettings["speed"] = 100; // 效果速度百分比
  
  // 预分配字符串内存
  String heartbeatMsg;
  heartbeatMsg.reserve(HEARTBEAT_JSON_SIZE);
  serializeJson(heartbeatDoc, heartbeatMsg);
  
  // 内存使用监控（降低日志频率）
  static unsigned long lastMemoryCheck = 0;
  unsigned long now = millis();
  if (now - lastMemoryCheck > HEARTBEAT_LOG_INTERVAL) {
    Serial.printf("💗 心跳: %dB, 内存: %dKB\n", heartbeatMsg.length(), ESP.getFreeHeap() / 1024);
    lastMemoryCheck = now;
  }
  
  publishMessage("heartbeat", heartbeatMsg);
}

// ===========================================
// 新增辅助方法实现
// ===========================================

bool MQTTManager::initializeSSLConnection() {
  if (mqttWiFiClient == nullptr) {
    mqttWiFiClient = new WiFiClientSecure();
    
    // 使用SSL证书进行安全连接
    const char* ca_cert = 
"-----BEGIN CERTIFICATE-----\n" 
"MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh\n" 
"MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\n" 
"d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD\n" 
"QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT\n" 
"MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j\n" 
"b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG\n" 
"9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB\n" 
"CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97\n" 
"nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt\n" 
"43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P\n" 
"T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4\n" 
"gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO\n" 
"BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR\n" 
"TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw\n" 
"DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr\n" 
"hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg\n" 
"06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF\n" 
"PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls\n" 
"YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk\n" 
"CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=\n" 
"-----END CERTIFICATE-----\n";
    
    mqttWiFiClient->setCACert(ca_cert);
    Serial.println("✅ SSL证书已配置 - DigiCert Global Root CA");
  }
  return true;
}

bool MQTTManager::checkMemoryAvailability() {
  size_t freeHeap = ESP.getFreeHeap();
  const size_t MIN_HEAP_SIZE = 20000; // 20KB 最小堆内存要求
  
  if (freeHeap < MIN_HEAP_SIZE) {
    Serial.printf("⚠️ 内存不足: 可用=%zu, 需要=%zu\n", freeHeap, MIN_HEAP_SIZE);
    return false;
  }
  return true;
}

void MQTTManager::logConnectionStatus() {
  Serial.printf("🔍 MQTT状态: 客户端=%s, 连接=%s, WiFi=%s, 内存=%dKB\n", 
                mqttClient ? "✅" : "❌",
                (mqttClient && mqttClient->connected()) ? "✅" : "❌",
                WiFi.isConnected() ? "✅" : "❌",
                ESP.getFreeHeap() / 1024);
}

bool MQTTManager::parseJsonMessage(const String& message, DynamicJsonDocument& doc) {
  DeserializationError error = deserializeJson(doc, message);
  if (error) {
    Serial.printf("❌ JSON解析失败: %s\n", error.c_str());
    publishError("Invalid JSON format: " + String(error.c_str()));
    return false;
  }
  return true;
}

void MQTTManager::printStatistics() {
  Serial.println("=== MQTT综合统计 ===");
  Serial.printf("🔗 连接状态: %s\n", isConnected() ? "✅已连接" : "❌未连接");
  Serial.printf("🔄 当前重连次数: %d\n", connectionRetries);
  Serial.printf("📊 内存使用: %dKB可用\n", ESP.getFreeHeap() / 1024);
  Serial.printf("📡 WiFi信号: %ddBm\n", WiFi.RSSI());
  Serial.printf("💗 上次心跳: %lums前\n", millis() - lastHeartbeat);
  
  // 显示关键性能指标
  Serial.printf("� 性能摘要:\n");
  Serial.printf("  连接成功率: ");
  if (totalConnectionAttempts > 0) {
    float rate = (float)successfulConnections / totalConnectionAttempts * 100;
    Serial.printf("%.1f%% (%lu/%lu)\n", rate, successfulConnections, totalConnectionAttempts);
  } else {
    Serial.println("N/A");
  }
  
  Serial.printf("  消息统计: 发送%lu/失败%lu/接收%lu\n", 
                totalMessagesSent, totalMessagesFailed, totalMessagesReceived);
  Serial.printf("  传输数据: %luKB\n", totalBytesTransmitted / 1024);
  
  Serial.println("==================");
}

// ===========================================
// 高级连接管理方法
// ===========================================

unsigned long MQTTManager::getNextReconnectInterval() {
  // 指数退避算法
  unsigned long interval = baseReconnectInterval;
  for (int i = 0; i < connectionRetries && i < 8; i++) { // 限制最大指数
    interval = (unsigned long)(interval * backoffMultiplier);
  }
  
  // 限制最大间隔
  if (interval > maxReconnectInterval) {
    interval = maxReconnectInterval;
  }
  
  return interval;
}

void MQTTManager::resetConnectionRetries() {
  connectionRetries = 0;
  consecutiveFailures = 0;
  baseReconnectInterval = RECONNECT_INTERVAL; // 重置为初始间隔
}

bool MQTTManager::shouldAttemptReconnect() {
  unsigned long now = millis();
  unsigned long requiredInterval = getNextReconnectInterval();
  
  // 检查是否到了重连时间
  if (now - lastReconnectAttempt < requiredInterval) {
    return false;
  }
  
  // 如果连续失败太多次，增加额外延迟
  if (consecutiveFailures > 10) {
    static unsigned long lastExtendedDelay = 0;
    if (now - lastExtendedDelay < 600000) { // 10分钟额外延迟
      return false;
    }
    lastExtendedDelay = now;
    consecutiveFailures = 0; // 重置连续失败计数
  }
  
  return true;
}

void MQTTManager::handleConnectionFailure() {
  connectionRetries++;
  consecutiveFailures++;
  
  Serial.printf("❌ MQTT连接失败 (第%d次, 连续失败%d次)\n", 
                connectionRetries, consecutiveFailures);
  
  if (connectionRetries >= MAX_CONNECTION_RETRIES) {
    Serial.printf("⚠️ 达到最大重试次数，延长重连间隔\n");
    connectionRetries = MAX_CONNECTION_RETRIES - 1; // 保持在最大值附近
  }
  
  // 如果连续失败太多，考虑重置网络连接
  if (consecutiveFailures > 20) {
    Serial.println("🔄 连续失败过多，建议检查网络连接");
    // 这里可以添加 WiFi 重连逻辑
  }
}

// ===========================================
// 日志管理方法
// ===========================================

void MQTTManager::setLogLevel(LogLevel level) {
  currentLogLevel = level;
  logInfo("日志级别设置为: " + String(getLogLevelString(level)));
}

MQTTManager::LogLevel MQTTManager::getLogLevel() const {
  return currentLogLevel;
}

const char* MQTTManager::getLogLevelString(LogLevel level) {
  switch (level) {
    case LOG_ERROR: return "ERROR";
    case LOG_WARN: return "WARN";
    case LOG_INFO: return "INFO";
    case LOG_DEBUG: return "DEBUG";
    default: return "UNKNOWN";
  }
}

void MQTTManager::log(LogLevel level, const String& message) {
  if (level <= currentLogLevel) {
    // 添加时间戳和日志级别前缀
    unsigned long timestamp = millis();
    const char* levelStr = getLogLevelString(level);
    
    // 选择合适的图标
    const char* icon = "ℹ️";
    switch (level) {
      case LOG_ERROR: icon = "❌"; break;
      case LOG_WARN: icon = "⚠️"; break;
      case LOG_INFO: icon = "ℹ️"; break;
      case LOG_DEBUG: icon = "🔍"; break;
    }
    
    Serial.printf("[%lu] %s [MQTT-%s] %s\n", timestamp, icon, levelStr, message.c_str());
  }
}

void MQTTManager::logError(const String& message) {
  log(LOG_ERROR, message);
}

void MQTTManager::logWarn(const String& message) {
  log(LOG_WARN, message);
}

void MQTTManager::logInfo(const String& message) {
  log(LOG_INFO, message);
}

void MQTTManager::logDebug(const String& message) {
  log(LOG_DEBUG, message);
}

// ===========================================
// 性能监控方法
// ===========================================

void MQTTManager::printPerformanceStats() {
  Serial.println("=== MQTT性能统计 ===");
  Serial.printf("🔗 连接统计:\n");
  Serial.printf("  尝试次数: %lu\n", totalConnectionAttempts);
  Serial.printf("  成功次数: %lu\n", successfulConnections);
  if (totalConnectionAttempts > 0) {
    float successRate = (float)successfulConnections / totalConnectionAttempts * 100;
    Serial.printf("  成功率: %.1f%%\n", successRate);
  }
  if (successfulConnections > 0) {
    float avgConnectionTime = (float)connectionTotalTime / successfulConnections;
    Serial.printf("  平均连接时间: %.1fms\n", avgConnectionTime);
  }
  
  Serial.printf("📨 消息统计:\n");
  Serial.printf("  发送成功: %lu\n", totalMessagesSent);
  Serial.printf("  发送失败: %lu\n", totalMessagesFailed);
  Serial.printf("  接收总数: %lu\n", totalMessagesReceived);
  Serial.printf("  传输字节: %lu\n", totalBytesTransmitted);
  
  if (totalMessagesSent + totalMessagesFailed > 0) {
    float msgSuccessRate = (float)totalMessagesSent / (totalMessagesSent + totalMessagesFailed) * 100;
    Serial.printf("  消息成功率: %.1f%%\n", msgSuccessRate);
  }
  
  Serial.printf("📊 连接质量:\n");
  Serial.printf("  连续失败: %d次\n", consecutiveFailures);
  if (lastSuccessfulConnection > 0) {
    unsigned long timeSinceLastSuccess = millis() - lastSuccessfulConnection;
    Serial.printf("  上次成功连接: %lums前\n", timeSinceLastSuccess);
  }
  
  Serial.println("====================");
}

void MQTTManager::resetStatistics() {
  totalConnectionAttempts = 0;
  successfulConnections = 0;
  totalMessagesSent = 0;
  totalMessagesReceived = 0;
  totalMessagesFailed = 0;
  totalBytesTransmitted = 0;
  connectionTotalTime = 0;
  consecutiveFailures = 0;
  
  logInfo("性能统计已重置");
}

// ===========================================
// 🔧 新增：播放列表管理命令处理
// ===========================================

void MQTTManager::handlePlaylistCommands(const String& command, const DynamicJsonDocument& payload) {
  Serial.println("📋 处理播放列表命令");
  Serial.printf("📥 接收到的命令: %s\n", command.c_str());
  
  // 🔧 调试：打印接收到的payload结构
  Serial.println("📄 Payload内容:");
  serializeJsonPretty(payload, Serial);
  Serial.println();
  
  // 验证必要字段（action在data对象内）
  if (!payload.containsKey("data") || !payload["data"].containsKey("action")) {
    Serial.println("❌ 缺少'data.action'字段");
    publishError("Playlist command missing 'data.action' field");
    return;
  }
  
  String action = payload["data"]["action"].as<String>();
  Serial.printf("📋 播放列表操作: %s\n", action.c_str());
  
  // 验证时间戳（可选但建议有）
  if (payload.containsKey("timestamp")) {
    unsigned long timestamp = payload["timestamp"].as<unsigned long>();
    Serial.printf("📅 命令时间戳: %lu\n", timestamp);
  }
  
  // 处理不同的播放列表操作
  bool result = processPlaylistAction(action, payload["data"]);
  
  if (result) {
    publishPlaylistResponse(action, "success", "Playlist " + action + " completed");
    Serial.printf("✅ 播放列表操作 '%s' 执行成功\n", action.c_str());
  } else {
    publishPlaylistResponse(action, "error", "Playlist " + action + " failed");
    Serial.printf("❌ 播放列表操作 '%s' 执行失败\n", action.c_str());
  }
}

bool MQTTManager::processPlaylistAction(const String& action, const DynamicJsonDocument& payload) {
  Serial.printf("🔄 处理播放列表操作: %s\n", action.c_str());
  
  if (action == "add") {
    Serial.println("➕ 处理添加模式到播放列表");
    
    // 验证patterns字段（payload已经是data对象）
    if (!payload.containsKey("patterns")) {
      Serial.println("❌ 缺少'patterns'字段");
      Serial.println("📄 payload内容:");
      serializeJsonPretty(payload, Serial);
      Serial.println();
      return false;
    }
    
    JsonArrayConst patterns = payload["patterns"];
    if (patterns.size() == 0) {
      Serial.println("❌ patterns 数组为空");
      return false;
    }
    
    Serial.printf("✅ 找到%d个模式需要添加\n", patterns.size());
    
    // 1. 读取现有播放列表
    DynamicJsonDocument existingPlaylist(4096);
    if (!loadPlaylistFromSD(existingPlaylist)) {
      Serial.println("💾 创建新的播放列表");
      existingPlaylist.to<JsonArray>();
    }
    
    JsonArray existingArray = existingPlaylist.as<JsonArray>();
    
    // 2. 处理每个新模式并下载THR文件
    for (JsonObjectConst pattern : patterns) {
      String patternId = pattern["id"] | "";
      String patternFile = pattern["pattern_file"] | "";
      
      // 从pattern_file URL中提取文件名
      String fileName = "";
      if (!patternFile.isEmpty()) {
        int lastSlash = patternFile.lastIndexOf('/');
        if (lastSlash >= 0) {
          fileName = patternFile.substring(lastSlash + 1);
        }
      }
      // 如果提取失败，使用patternId作为文件名
      if (fileName.isEmpty()) {
        fileName = patternId + ".thr";
      }
      
      Serial.printf("📝 添加模式: ID=%s, 文件URL=%s, 文件名=%s\n", 
                   patternId.c_str(), patternFile.c_str(), fileName.c_str());
      
      // 检查模式是否已存在（避免重复）
      bool exists = false;
      for (JsonObjectConst existing : existingArray) {
        if (String(existing["id"] | "") == patternId) {
          exists = true;
          Serial.printf("⚠️ 模式ID %s 已存在，跳过\n", patternId.c_str());
          break;
        }
      }
      
      if (!exists) {
        // 创建新模式对象
        JsonObject newPattern = existingArray.createNestedObject();
        newPattern["id"] = patternId;
        newPattern["pattern_file"] = patternFile;
        newPattern["file_name"] = fileName;
        
        // 下载THR文件到SD卡
        if (!patternFile.isEmpty()) {
          String localPath = "/patterns/" + fileName;
          Serial.printf("⬇️ 下载THR文件: %s -> %s\n", patternFile.c_str(), localPath.c_str());
          if (downloadTHRFileWithName(patternFile, patternId, fileName)) {
            Serial.printf("✅ 成功下载THR文件: %s\n", fileName.c_str());
          } else {
            Serial.printf("❌ 下载THR文件失败: %s\n", fileName.c_str());
          }
        }
        
        Serial.printf("✅ 已添加模式: %s\n", patternId.c_str());
      }
    }
    
    // 3. 保存更新后的播放列表到SD卡
    if (savePlaylistToSD(existingPlaylist)) {
      Serial.printf("💾 播放列表已保存，共%d个模式\n", existingArray.size());
      return true;
    } else {
      Serial.println("❌ 播放列表保存失败");
      return false;
    }
    
  } else if (action == "remove") {
    Serial.println("➖ 处理从播放列表移除模式");
    
    // 验证removed_pattern_ids字段
    if (!payload.containsKey("removed_pattern_ids")) {
      Serial.println("❌ 缺少'removed_pattern_ids'字段");
      return false;
    }
    
    JsonArrayConst removedIds = payload["removed_pattern_ids"];
    if (removedIds.size() == 0) {
      Serial.println("❌ removed_pattern_ids 数组为空");
      return false;
    }
    
    // 1. 读取现有播放列表
    DynamicJsonDocument existingPlaylist(4096);
    if (!loadPlaylistFromSD(existingPlaylist)) {
      Serial.println("❌ 播放列表文件不存在，无法移除");
      return false;
    }
    
    JsonArray existingArray = existingPlaylist.as<JsonArray>();
    
    // 2. 移除指定的模式
    int removedCount = 0;
    for (const String& idToRemove : removedIds) {
      Serial.printf("🗑️ 查找并移除模式ID: %s\n", idToRemove.c_str());
      
      for (size_t i = 0; i < existingArray.size(); ) {
        String existingId = existingArray[i]["id"] | "";
        if (existingId == idToRemove) {
          existingArray.remove(i);
          removedCount++;
          Serial.printf("✅ 已移除模式: %s\n", idToRemove.c_str());
          // 不递增i，因为数组元素已经移除
        } else {
          i++;
        }
      }
    }
    
    // 3. 保存更新后的播放列表
    if (savePlaylistToSD(existingPlaylist)) {
      Serial.printf("💾 播放列表已更新，移除了%d个模式，剩余%d个模式\n", 
                   removedCount, existingArray.size());
      return true;
    } else {
      Serial.println("❌ 播放列表保存失败");
      return false;
    }
    
  } else if (action == "clear") {
    Serial.println("🧹 处理清空播放列表");
    
    // 创建空的播放列表
    DynamicJsonDocument emptyPlaylist(1024);
    emptyPlaylist.to<JsonArray>();
    
    // 保存空播放列表到SD卡
    if (savePlaylistToSD(emptyPlaylist)) {
      Serial.println("💾 播放列表已清空");
      return true;
    } else {
      Serial.println("❌ 清空播放列表失败");
      return false;
    }
    
  } else if (action == "playNow") {
    Serial.println("▶️ 处理立即播放（清空并添加）");
    
    // 验证patterns字段
    if (!payload.containsKey("patterns")) {
      Serial.println("❌ 缺少'patterns'字段");
      return false;
    }
    
    JsonArrayConst patterns = payload["patterns"];
    if (patterns.size() == 0) {
      Serial.println("❌ patterns 数组为空");
      return false;
    }
    
    // 1. 创建新的播放列表（清空现有的）
    DynamicJsonDocument newPlaylist(4096);
    JsonArray newArray = newPlaylist.to<JsonArray>();
    
    // 2. 添加所有新模式并下载THR文件
    for (JsonObjectConst pattern : patterns) {
      String patternId = pattern["id"] | "";
      String patternFile = pattern["pattern_file"] | "";
      
      // 从pattern_file URL中提取文件名
      String fileName = "";
      if (!patternFile.isEmpty()) {
        int lastSlash = patternFile.lastIndexOf('/');
        if (lastSlash >= 0) {
          fileName = patternFile.substring(lastSlash + 1);
        }
      }
      // 如果提取失败，使用patternId作为文件名
      if (fileName.isEmpty()) {
        fileName = patternId + ".thr";
      }
      
      Serial.printf("📝 添加模式到播放: ID=%s, 文件URL=%s, 文件名=%s\n", 
                   patternId.c_str(), patternFile.c_str(), fileName.c_str());
      
      // 创建新模式对象
      JsonObject newPattern = newArray.createNestedObject();
      newPattern["id"] = patternId;
      newPattern["pattern_file"] = patternFile;
      newPattern["file_name"] = fileName;
      
      // 下载THR文件到SD卡
      if (!patternFile.isEmpty()) {
        String localPath = "/patterns/" + fileName;
        Serial.printf("⬇️ 下载THR文件: %s -> %s\n", patternFile.c_str(), localPath.c_str());
        if (downloadTHRFileWithName(patternFile, patternId, fileName)) {
          Serial.printf("✅ 成功下载THR文件: %s\n", fileName.c_str());
        } else {
          Serial.printf("❌ 下载THR文件失败: %s\n", fileName.c_str());
        }
      }
    }
    
    // 3. 保存新播放列表到SD卡
    if (savePlaylistToSD(newPlaylist)) {
      Serial.printf("💾 立即播放列表已保存，共%d个模式\n", newArray.size());
      // TODO: 触发立即开始播放第一个模式
      return true;
    } else {
      Serial.println("❌ 立即播放列表保存失败");
      return false;
    }
    
    return true;
    
  } else if (action == "clear") {
    Serial.println("🧹 处理清空播放列表");
    
    // 🔧 创建空的播放列表并保存到SD卡
    DynamicJsonDocument emptyPlaylist(512);
    emptyPlaylist["patterns"] = JsonArray();
    emptyPlaylist["play_mode"] = 0;
    emptyPlaylist["current_playing"] = nullptr;
    
    if (savePlaylistToSD(emptyPlaylist)) {
      Serial.println("💾 空播放列表已保存到SD卡");
    } else {
      Serial.println("❌ 清空播放列表保存失败");
      return false;
    }
    
    return true;
    
  } else if (action == "update") {
    Serial.println("🔄 处理播放列表更新");
    
    if (payload.containsKey("options")) {
      String updatedField = payload["options"]["updated_field"] | "";
      Serial.printf("🔄 更新字段: %s\n", updatedField.c_str());
      
      if (updatedField == "play_mode") {
        int newPlayMode = payload["options"]["play_mode"] | 0;
        Serial.printf("🔄 更新播放模式: %d\n", newPlayMode);
      }
    }
    
    // 🔧 保存更新后的播放列表到SD卡
    if (payload.containsKey("data")) {
      DynamicJsonDocument playlistDoc(2048);
      playlistDoc.set(payload["data"]);
      if (savePlaylistToSD(playlistDoc)) {
        Serial.println("💾 更新后的播放列表已保存到SD卡");
      } else {
        Serial.println("❌ 更新后的播放列表保存失败");
        return false;
      }
    }
    
    return true;
    
  } else if (action == "view") {
    Serial.println("👁️ 处理查看播放列表文件内容");
    
    // 读取并返回SD卡上的playlist.json文件内容
    return viewPlaylistFile();
    
  } else {
    Serial.printf("❓ 未知的播放列表操作: %s\n", action.c_str());
    return false;
  }
}

void MQTTManager::publishPlaylistResponse(const String& action, const String& status, const String& message) {
  DynamicJsonDocument responseDoc(512);
  
  responseDoc["cmd"] = "playlist";
  responseDoc["action"] = action;
  responseDoc["status"] = status;
  responseDoc["timestamp"] = millis();
  responseDoc["device_sn"] = deviceSN;
  
  if (message.length() > 0) {
    responseDoc["message"] = message;
  }
  
  String response;
  serializeJson(responseDoc, response);
  
  publishMessage("response", response);
}

// ===========================================
// 🔧 新增：THR文件下载命令处理
// ===========================================

void MQTTManager::handleDownloadCommands(const String& command, const DynamicJsonDocument& payload) {
  Serial.println("⬇️ 处理THR文件下载命令");
  
  // 验证必要字段
  if (!payload.containsKey("action")) {
    publishError("Download command missing 'action' field");
    return;
  }
  
  String action = payload["action"].as<String>();
  Serial.printf("⬇️ 下载操作: %s\n", action.c_str());
  
  if (action == "batch") {
    Serial.println("📦 处理批量下载");
    
    if (!payload.containsKey("data") || !payload["data"].containsKey("patterns")) {
      publishError("Batch download missing patterns data");
      return;
    }
    
    JsonArrayConst patterns = payload["data"]["patterns"];
    int totalPatterns = patterns.size();
    int successCount = 0;
    
    Serial.printf("📦 开始批量下载 %d 个THR文件\n", totalPatterns);
    
    for (JsonObjectConst pattern : patterns) {
      String patternId = pattern["id"] | "";
      String thrUrl = pattern["thr_url"] | "";
      String checksum = pattern["checksum"] | "";
      
      Serial.printf("⬇️ 下载文件 %d/%d: %s\n", successCount + 1, totalPatterns, patternId.c_str());
      
      if (downloadTHRFile(thrUrl, patternId, checksum)) {
        successCount++;
        publishDownloadProgress(patternId, (successCount * 100) / totalPatterns, "downloading");
      } else {
        publishDownloadProgress(patternId, 0, "error");
      }
      
      // 防止下载过程中阻塞太久
      yield();
      delay(100);
    }
    
    // 发布批量下载完成状态
    DynamicJsonDocument batchResponse(512);
    batchResponse["cmd"] = "download";
    batchResponse["action"] = "batch";
    batchResponse["status"] = (successCount == totalPatterns) ? "completed" : "partial";
    batchResponse["total"] = totalPatterns;
    batchResponse["success"] = successCount;
    batchResponse["failed"] = totalPatterns - successCount;
    batchResponse["timestamp"] = millis();
    
    String response;
    serializeJson(batchResponse, response);
    publishMessage("response", response);
    
    Serial.printf("📦 批量下载完成: %d/%d 成功\n", successCount, totalPatterns);
    
  } else if (action == "single") {
    Serial.println("📄 处理单个文件下载");
    
    if (!payload.containsKey("data")) {
      publishError("Single download missing data");
      return;
    }
    
    String patternId = payload["data"]["pattern_id"] | "";
    String thrUrl = payload["data"]["thr_url"] | "";
    String checksum = payload["data"]["checksum"] | "";
    
    Serial.printf("📄 下载单个文件: %s\n", patternId.c_str());
    
    publishDownloadProgress(patternId, 0, "starting");
    
    bool success = downloadTHRFile(thrUrl, patternId, checksum);
    
    // 发布单个下载完成状态
    DynamicJsonDocument singleResponse(512);
    singleResponse["cmd"] = "download";
    singleResponse["action"] = "single";
    singleResponse["pattern_id"] = patternId;
    singleResponse["status"] = success ? "completed" : "error";
    singleResponse["timestamp"] = millis();
    
    String response;
    serializeJson(singleResponse, response);
    publishMessage("response", response);
    
    Serial.printf("📄 单个文件下载%s: %s\n", success ? "成功" : "失败", patternId.c_str());
    
  } else {
    Serial.printf("❓ 未知的下载操作: %s\n", action.c_str());
    publishError("Unknown download action: " + action);
  }
}

bool MQTTManager::downloadTHRFile(const String& url, const String& patternId, const String& checksum) {
  Serial.printf("🌐 开始分片下载THR文件: %s\n", url.c_str());
  
  // 分片下载配置
  const size_t CHUNK_SIZE = 4096; // 4KB per chunk - 更小的分片大小
  const int MAX_RETRIES = 3;
  const int CHUNKS_PER_PROGRESS_UPDATE = 5; // 每5个分片更新一次进度
  
  Serial.println("🔧 开始内存优化模式...");
  
  // 1. 临时暂停非关键功能
  bool mqttWasConnected = isConnected();
  if (mqttWasConnected) {
    Serial.println("⏸️ 临时断开MQTT连接以释放内存");
    disconnect();
    delay(100);
  }
  
  // 2. 强制垃圾回收
  ESP.getHeapSize(); // 触发内存整理
  yield();
  delay(200);
  
  // 检查内存可用性
  if (!checkMemoryAvailability()) {
    Serial.println("❌ 内存不足，无法下载文件");
    // 恢复MQTT连接
    if (mqttWasConnected) {
      connect();
    }
    return false;
  }
  
  // 检查URL有效性
  if (url.length() == 0 || patternId.length() == 0) {
    Serial.println("❌ 下载参数无效");
    return false;
  }
  
  // 强制内存清理
  ESP.getHeapSize(); // 触发内存整理
  yield();
  delay(100);
  
  // 检查可用内存 - 分片下载只需要很少内存
  size_t freeHeap = ESP.getFreeHeap();
  Serial.printf("🔍 当前可用内存: %zu 字节\n", freeHeap);
  
  // 分片下载只需要8KB内存即可
  if (freeHeap < 8000) {
    Serial.printf("⚠️ 内存不足，无法下载: 可用=%zu, 需要=8000\n", freeHeap);
    publishDownloadProgress(patternId, 0, "failed");
    
    // 恢复MQTT连接
    if (mqttWasConnected && !isConnected()) {
      Serial.println("🔄 恢复MQTT连接...");
      delay(1000);
      connect();
    }
    return false;
  }
  
  // 1. 首先获取文件大小
  int fileSize = getFileSize(url);
  if (fileSize <= 0) {
    Serial.println("❌ 无法获取文件大小");
    publishDownloadProgress(patternId, 0, "failed");
    
    // 恢复MQTT连接
    if (mqttWasConnected && !isConnected()) {
      Serial.println("🔄 恢复MQTT连接...");
      delay(1000);
      connect();
    }
    return false;
  }
  
  Serial.printf("📦 文件大小: %d 字节，分片大小: %d 字节\n", fileSize, CHUNK_SIZE);
  
  // 2. 创建patterns目录
  if (!SD.exists("/patterns")) {
    SD.mkdir("/patterns");
    Serial.println("📁 创建patterns目录");
  }
  
  // 3. 创建目标文件
  String filePath = "/patterns/" + patternId + ".thr";
  File file = SD.open(filePath, FILE_WRITE);
  if (!file) {
    Serial.printf("❌ 无法创建文件: %s\n", filePath.c_str());
    publishDownloadProgress(patternId, 0, "failed");
    
    // 恢复MQTT连接
    if (mqttWasConnected && !isConnected()) {
      Serial.println("🔄 恢复MQTT连接...");
      delay(1000);
      connect();
    }
    return false;
  }
  
  // 4. 开始分片下载
  bool downloadSuccess = downloadInChunks(url, file, fileSize, patternId, CHUNK_SIZE, CHUNKS_PER_PROGRESS_UPDATE);
  
  file.close();
  
  // 5. 处理下载结果
  if (downloadSuccess) {
    Serial.printf("✅ THR文件分片下载完成: /patterns/%s.thr (%d字节)\n", patternId.c_str(), fileSize);
    
    // 稍等片刻让内存稳定
    delay(100);
    
    // 检查最终内存状态
    freeHeap = ESP.getFreeHeap();
    Serial.printf("🔍 下载完成后可用内存: %zu 字节\n", freeHeap);
    
    publishDownloadProgress(patternId, 100, "completed");
  } else {
    Serial.println("❌ 分片下载失败");
    publishDownloadProgress(patternId, 0, "failed");
    
    // 删除不完整的文件
    String filePath = "/patterns/" + patternId + ".thr";
    if (SD.exists(filePath)) {
      SD.remove(filePath);
      Serial.printf("🗑️ 已删除不完整的文件: %s\n", filePath.c_str());
    }
  }
  
  // 6. 恢复MQTT连接
  if (mqttWasConnected && !isConnected()) {
    Serial.println("🔄 恢复MQTT连接...");
    delay(1000);
    connect();
  }
  
  return downloadSuccess;

}

void MQTTManager::publishDownloadProgress(const String& patternId, int progress, const String& status) {
  // 检查内存状态，避免在内存不足时强制发布
  size_t freeHeap = ESP.getFreeHeap();
  
  // 减少进度日志输出频率
  static unsigned long lastProgressLog = 0;
  unsigned long now = millis();
  if (now - lastProgressLog > 5000 || progress == 100) { // 每5秒或完成时打印
    Serial.printf("📊 下载进度: %s - %d%% (%s)\n", patternId.c_str(), progress, status.c_str());
    lastProgressLog = now;
  }
  
  // 只有足够内存时才发布MQTT消息
  if (freeHeap < 15000) {
    Serial.printf("⚠️ 内存不足，跳过消息发布: 可用=%zuKB\n", freeHeap / 1024);
    return;
  }
  
  DynamicJsonDocument progressDoc(256);
  
  progressDoc["cmd"] = "download_progress";
  progressDoc["pattern_id"] = patternId;
  progressDoc["progress"] = progress;
  progressDoc["status"] = status;
  progressDoc["timestamp"] = millis();
  
  String progressMsg;
  serializeJson(progressDoc, progressMsg);
  
  publishMessage("progress", progressMsg);
  Serial.printf("� 发布MQTT消息: /SANDART001/340766215788/progress, %d字节\n", progressMsg.length());
}

// ===========================================
// 🔧 新增：SD卡播放列表管理方法实现
// ===========================================

String MQTTManager::getPlaylistFilePath() const {
  return PLAYLIST_FILE_PATH;
}

bool MQTTManager::loadPlaylistFromSD() {
  if (!currentPlaylist) {
    Serial.println("❌ 播放列表对象未初始化");
    return false;
  }
  
  // 检查SD卡是否已初始化
  if (!SD.begin()) {
    Serial.println("⚠️ SD卡未初始化，跳过播放列表加载");
    return false;
  }
  
  // 检查播放列表文件是否存在
  if (!SD.exists(PLAYLIST_FILE_PATH)) {
    Serial.println("📝 播放列表文件不存在，创建默认播放列表");
    
    // 创建默认的空播放列表
    currentPlaylist->clear();
    (*currentPlaylist)["patterns"] = JsonArray();
    (*currentPlaylist)["play_mode"] = 0;
    (*currentPlaylist)["current_playing"] = nullptr;
    (*currentPlaylist)["created_at"] = millis();
    (*currentPlaylist)["updated_at"] = millis();
    
    // 保存默认播放列表到SD卡
    return savePlaylistToSD(*currentPlaylist);
  }
  
  // 读取播放列表文件
  File playlistFile = SD.open(PLAYLIST_FILE_PATH, FILE_READ);
  if (!playlistFile) {
    Serial.println("❌ 无法打开播放列表文件进行读取");
    return false;
  }
  
  // 获取文件大小
  size_t fileSize = playlistFile.size();
  if (fileSize == 0) {
    Serial.println("⚠️ 播放列表文件为空");
    playlistFile.close();
    return false;
  }
  
  if (fileSize > 8192) { // 限制播放列表文件大小为8KB
    Serial.printf("⚠️ 播放列表文件过大: %zu字节，限制为8KB\n", fileSize);
    playlistFile.close();
    return false;
  }
  
  // 读取文件内容
  String jsonContent = playlistFile.readString();
  playlistFile.close();
  
  // 解析JSON
  currentPlaylist->clear();
  DeserializationError error = deserializeJson(*currentPlaylist, jsonContent);
  
  if (error) {
    Serial.printf("❌ 播放列表JSON解析失败: %s\n", error.c_str());
    
    // 创建默认播放列表
    currentPlaylist->clear();
    (*currentPlaylist)["patterns"] = JsonArray();
    (*currentPlaylist)["play_mode"] = 0;
    (*currentPlaylist)["current_playing"] = nullptr;
    
    return false;
  }
  
  // 验证播放列表结构
  if (!currentPlaylist->containsKey("patterns")) {
    Serial.println("⚠️ 播放列表缺少patterns字段，补充默认值");
    (*currentPlaylist)["patterns"] = JsonArray();
  }
  
  if (!currentPlaylist->containsKey("play_mode")) {
    (*currentPlaylist)["play_mode"] = 0;
  }
  
  // 打印加载信息
  JsonArrayConst patterns = (*currentPlaylist)["patterns"];
  int patternCount = patterns.size();
  int playMode = (*currentPlaylist)["play_mode"] | 0;
  
  Serial.printf("✅ 播放列表加载成功: %d个模式, 播放模式=%d\n", patternCount, playMode);
  
  // 打印前几个模式信息
  for (int i = 0; i < min(patternCount, 3); i++) {
    JsonObjectConst pattern = patterns[i];
    String patternId = pattern["id"] | "未知";
    String patternName = pattern["name"] | "未命名";
    Serial.printf("  模式%d: %s (%s)\n", i + 1, patternName.c_str(), patternId.c_str());
  }
  
  if (patternCount > 3) {
    Serial.printf("  ... 还有 %d 个模式\n", patternCount - 3);
  }
  
  return true;
}

// 重载版本：加载播放列表到指定的DynamicJsonDocument
bool MQTTManager::loadPlaylistFromSD(DynamicJsonDocument& playlistData) {
  // 检查SD卡是否已初始化
  if (!SD.begin()) {
    Serial.println("⚠️ SD卡未初始化，跳过播放列表加载");
    return false;
  }
  
  // 检查播放列表文件是否存在
  if (!SD.exists(PLAYLIST_FILE_PATH)) {
    Serial.println("📝 播放列表文件不存在，创建空播放列表");
    playlistData.to<JsonArray>();
    return false;
  }
  
  // 打开文件
  File playlistFile = SD.open(PLAYLIST_FILE_PATH, FILE_READ);
  if (!playlistFile) {
    Serial.println("❌ 无法打开播放列表文件");
    playlistData.to<JsonArray>();
    return false;
  }
  
  // 检查文件大小
  size_t fileSize = playlistFile.size();
  if (fileSize == 0) {
    Serial.println("⚠️ 播放列表文件为空");
    playlistFile.close();
    playlistData.to<JsonArray>();
    return false;
  }
  
  if (fileSize > 8192) {
    Serial.printf("⚠️ 播放列表文件过大: %zu字节，限制为8KB\n", fileSize);
    playlistFile.close();
    playlistData.to<JsonArray>();
    return false;
  }
  
  // 读取并解析JSON
  String jsonContent = playlistFile.readString();
  playlistFile.close();
  
  DeserializationError error = deserializeJson(playlistData, jsonContent);
  if (error) {
    Serial.printf("❌ 播放列表JSON解析失败: %s\n", error.c_str());
    playlistData.to<JsonArray>();
    return false;
  }
  
  // 确保结果是数组格式
  if (!playlistData.is<JsonArray>()) {
    Serial.println("⚠️ 播放列表不是数组格式，转换为数组");
    playlistData.to<JsonArray>();
    return false;
  }
  
  JsonArrayConst patterns = playlistData.as<JsonArrayConst>();
  Serial.printf("✅ 播放列表加载成功: %d个模式\n", patterns.size());
  
  return true;
}

bool MQTTManager::savePlaylistToSD(const DynamicJsonDocument& playlistData) {
  // 检查SD卡是否可用
  if (!SD.begin()) {
    Serial.println("❌ SD卡未初始化，无法保存播放列表");
    return false;
  }
  
  // 检查内存可用性
  if (!checkMemoryAvailability()) {
    Serial.println("❌ 内存不足，无法保存播放列表");
    return false;
  }
  
  // 创建临时文件进行原子写入
  String tempFilePath = PLAYLIST_FILE_PATH + ".tmp";
  
  // 删除可能存在的临时文件
  if (SD.exists(tempFilePath)) {
    SD.remove(tempFilePath);
  }
  
  // 创建临时文件
  File tempFile = SD.open(tempFilePath, FILE_WRITE);
  if (!tempFile) {
    Serial.println("❌ 无法创建播放列表临时文件");
    return false;
  }
  
  // 创建包含时间戳的完整播放列表数据
  DynamicJsonDocument fullPlaylistData(playlistData.capacity() + 256);
  fullPlaylistData.set(playlistData);
  fullPlaylistData["updated_at"] = millis();
  fullPlaylistData["device_sn"] = deviceSN;
  
  // 如果没有created_at字段，添加它
  if (!fullPlaylistData.containsKey("created_at")) {
    fullPlaylistData["created_at"] = millis();
  }
  
  // 序列化JSON到文件
  size_t bytesWritten = serializeJson(fullPlaylistData, tempFile);
  tempFile.close();
  
  if (bytesWritten == 0) {
    Serial.println("❌ 播放列表JSON序列化失败");
    SD.remove(tempFilePath);
    return false;
  }
  
  // 原子性替换：删除旧文件，重命名临时文件
  if (SD.exists(PLAYLIST_FILE_PATH)) {
    if (!SD.remove(PLAYLIST_FILE_PATH)) {
      Serial.println("❌ 无法删除旧的播放列表文件");
      SD.remove(tempFilePath);
      return false;
    }
  }
  
  // 这里我们使用复制+删除的方式，因为ESP32的SD库可能不支持rename
  File sourceFile = SD.open(tempFilePath, FILE_READ);
  File destFile = SD.open(PLAYLIST_FILE_PATH, FILE_WRITE);
  
  if (!sourceFile || !destFile) {
    Serial.println("❌ 无法执行播放列表文件原子替换");
    if (sourceFile) sourceFile.close();
    if (destFile) destFile.close();
    SD.remove(tempFilePath);
    return false;
  }
  
  // 复制内容
  while (sourceFile.available()) {
    destFile.write(sourceFile.read());
  }
  
  sourceFile.close();
  destFile.close();
  
  // 删除临时文件
  SD.remove(tempFilePath);
  
  // 更新内存中的播放列表
  if (currentPlaylist) {
    currentPlaylist->set(fullPlaylistData);
  }
  
  // 打印保存信息
  if (playlistData.containsKey("patterns")) {
    JsonArrayConst patterns = playlistData["patterns"];
    int patternCount = patterns.size();
    Serial.printf("💾 播放列表保存成功: %d个模式, %zu字节\n", patternCount, bytesWritten);
  } else {
    Serial.printf("💾 播放列表保存成功: %zu字节\n", bytesWritten);
  }
  
  return true;
}

bool MQTTManager::updatePlaylistOnSD(const String& action, const DynamicJsonDocument& payload) {
  // 这是一个统一的更新方法，可以根据不同的action执行相应的更新操作
  Serial.printf("🔄 更新SD卡播放列表: %s\n", action.c_str());
  
  // 确保播放列表已加载
  if (!currentPlaylist) {
    Serial.println("❌ 播放列表未初始化");
    return false;
  }
  
  // 根据action执行相应的更新
  if (action == "add") {
    // 添加新模式到现有播放列表
    if (payload.containsKey("data") && payload["data"].containsKey("patterns")) {
      JsonArrayConst newPatterns = payload["data"]["patterns"];
      JsonArray currentPatterns = (*currentPlaylist)["patterns"];
      
      // 添加新模式
      for (JsonObjectConst newPattern : newPatterns) {
        JsonObject addedPattern = currentPatterns.createNestedObject();
        addedPattern.set(newPattern);
      }
      
      // 更新播放模式
      if (payload["data"].containsKey("play_mode")) {
        (*currentPlaylist)["play_mode"] = payload["data"]["play_mode"];
      }
      
      Serial.printf("📝 添加了 %d 个新模式到播放列表\n", newPatterns.size());
    }
    
  } else if (action == "remove") {
    // 从播放列表中移除指定模式
    if (payload.containsKey("options") && payload["options"].containsKey("removed_pattern_id")) {
      String removedId = payload["options"]["removed_pattern_id"].as<String>();
      JsonArray patterns = (*currentPlaylist)["patterns"];
      
      // 查找并移除指定模式
      for (int i = patterns.size() - 1; i >= 0; i--) {
        JsonObject pattern = patterns[i];
        String patternId = pattern["id"] | "";
        if (patternId == removedId) {
          patterns.remove(i);
          Serial.printf("🗑️ 已从播放列表移除模式: %s\n", removedId.c_str());
          break;
        }
      }
    }
    
  } else if (action == "clear") {
    // 清空播放列表
    JsonArray patterns = (*currentPlaylist)["patterns"];
    patterns.clear();
    (*currentPlaylist)["current_playing"] = nullptr;
    Serial.println("🧹 播放列表已清空");
    
  } else {
    // 对于其他操作，直接使用payload中的data替换当前播放列表
    if (payload.containsKey("data")) {
      currentPlaylist->set(payload["data"]);
      Serial.printf("🔄 播放列表已更新: %s\n", action.c_str());
    }
  }
  
  // 保存更新后的播放列表
  return savePlaylistToSD(*currentPlaylist);
}

// ===========================================
// 播放列表访问方法实现
// ===========================================

const DynamicJsonDocument* MQTTManager::getCurrentPlaylist() const {
  return currentPlaylist;
}

String MQTTManager::getPlaylistSummary() const {
  if (!currentPlaylist || currentPlaylist->isNull()) {
    return "播放列表未加载";
  }
  
  JsonArrayConst patterns = (*currentPlaylist)["patterns"];
  int patternCount = patterns.size();
  int playMode = (*currentPlaylist)["play_mode"] | 0;
  String currentPlaying = (*currentPlaylist)["current_playing"] | "无";
  
  String summary = "播放列表摘要:\n";
  summary += "  模式数量: " + String(patternCount) + "\n";
  summary += "  播放模式: " + String(playMode) + "\n";
  summary += "  当前播放: " + currentPlaying + "\n";
  
  if (patternCount > 0) {
    summary += "  前几个模式:\n";
    for (int i = 0; i < min(patternCount, 5); i++) {
      JsonObjectConst pattern = patterns[i];
      String patternName = pattern["name"] | "未命名";
      String patternId = pattern["id"] | "未知ID";
      summary += "    " + String(i + 1) + ". " + patternName + " (" + patternId + ")\n";
    }
    
    if (patternCount > 5) {
      summary += "    ... 还有 " + String(patternCount - 5) + " 个模式\n";
    }
  }
  
  return summary;
}

bool MQTTManager::viewPlaylistFile() {
  Serial.println("👁️ 查看SD卡播放列表文件内容");
  
  // 检查SD卡是否已初始化
  if (!SD.begin()) {
    Serial.println("❌ SD卡未初始化");
    publishPlaylistResponse("view", "error", "SD card not initialized");
    return false;
  }
  
  // 检查播放列表文件是否存在
  if (!SD.exists(PLAYLIST_FILE_PATH)) {
    Serial.println("❌ 播放列表文件不存在");
    publishPlaylistResponse("view", "error", "Playlist file does not exist");
    return false;
  }
  
  // 获取文件信息
  File file = SD.open(PLAYLIST_FILE_PATH, FILE_READ);
  if (!file) {
    Serial.println("❌ 无法打开播放列表文件");
    publishPlaylistResponse("view", "error", "Cannot open playlist file");
    return false;
  }
  
  size_t fileSize = file.size();
  Serial.printf("📄 播放列表文件大小: %zu 字节\n", fileSize);
  
  // 读取文件内容
  String fileContent = "";
  while (file.available()) {
    fileContent += (char)file.read();
  }
  file.close();
  
  Serial.printf("📄 播放列表文件内容:\n%s\n", fileContent.c_str());
  
  // 尝试解析JSON来验证格式
  DynamicJsonDocument testDoc(4096);
  DeserializationError error = deserializeJson(testDoc, fileContent);
  
  String status = "success";
  String message = "";
  
  if (error) {
    status = "warning";
    message = "File exists but JSON format is invalid: " + String(error.c_str());
    Serial.printf("⚠️ JSON格式无效: %s\n", error.c_str());
  } else {
    // 获取一些基本统计信息
    int patternCount = 0;
    if (testDoc.containsKey("patterns") && testDoc["patterns"].is<JsonArray>()) {
      patternCount = testDoc["patterns"].size();
    }
    
    unsigned long updatedAt = testDoc["updated_at"] | 0;
    unsigned long createdAt = testDoc["created_at"] | 0;
    String deviceSN = testDoc["device_sn"] | "unknown";
    
    message = "File contains " + String(patternCount) + " patterns, device: " + deviceSN;
    Serial.printf("✅ 播放列表包含 %d 个模式，设备: %s\n", patternCount, deviceSN.c_str());
    Serial.printf("📅 创建时间: %lu, 更新时间: %lu\n", createdAt, updatedAt);
  }
  
  // 通过MQTT发布文件内容
  DynamicJsonDocument response(1024 + fileContent.length());
  response["cmd"] = "playlist";
  response["action"] = "view";
  response["status"] = status;
  response["message"] = message;
  response["file_size"] = fileSize;
  response["file_path"] = PLAYLIST_FILE_PATH;
  response["content"] = fileContent;
  response["timestamp"] = millis();
  
  String responseMsg;
  serializeJson(response, responseMsg);
  
  publishMessage("response", responseMsg);
  Serial.printf("📤 已通过MQTT发布播放列表文件内容 (%zu字节)\n", responseMsg.length());
  
  return true;
}

// ===========================================
// 🔧 分片下载辅助函数
// ===========================================

int MQTTManager::getFileSize(const String& url) {
  WiFiClientSecure client;
  client.setInsecure();
  
  HTTPClient http;
  http.begin(client, url);
  
  // 发送HEAD请求获取文件大小
  int httpCode = http.sendRequest("HEAD");
  
  if (httpCode == HTTP_CODE_OK) {
    int size = http.getSize();
    http.end();
    return size;
  }
  
  http.end();
  return -1;
}

bool MQTTManager::downloadInChunks(const String& url, File& file, int fileSize, const String& patternId, 
                                  size_t chunkSize, int chunksPerProgressUpdate) {
  int downloaded = 0;
  int chunkCount = 0;
  int totalChunks = (fileSize + chunkSize - 1) / chunkSize;
  
  publishDownloadProgress(patternId, 0, "downloading");
  
  while (downloaded < fileSize) {
    // 计算当前分片的实际大小
    size_t currentChunkSize = min(chunkSize, (size_t)(fileSize - downloaded));
    
    // 计算Range头部
    String rangeHeader = "bytes=" + String(downloaded) + "-" + String(downloaded + currentChunkSize - 1);
    
    // 创建HTTP请求
    WiFiClientSecure client;
    client.setInsecure();
    
    HTTPClient http;
    http.begin(client, url);
    http.addHeader("Range", rangeHeader);
    
    Serial.printf("📥 下载分片 %d/%d: %s\n", chunkCount + 1, totalChunks, rangeHeader.c_str());
    
    int httpCode = http.GET();
    
    if (httpCode == HTTP_CODE_PARTIAL_CONTENT || httpCode == HTTP_CODE_OK) {
      WiFiClient* stream = http.getStreamPtr();
      
      // 分配缓冲区
      uint8_t* buffer = (uint8_t*)malloc(currentChunkSize);
      if (!buffer) {
        Serial.println("❌ 无法分配分片缓冲区");
        http.end();
        return false;
      }
      
      // 读取数据
      size_t bytesRead = 0;
      while (bytesRead < currentChunkSize && http.connected()) {
        size_t available = stream->available();
        if (available > 0) {
          size_t toRead = min(available, currentChunkSize - bytesRead);
          size_t actualRead = stream->readBytes(buffer + bytesRead, toRead);
          bytesRead += actualRead;
        }
        yield();
      }
      
      // 写入文件
      if (bytesRead > 0) {
        file.write(buffer, bytesRead);
        downloaded += bytesRead;
      }
      
      free(buffer);
      http.end();
      
      // 更新进度
      chunkCount++;
      if (chunkCount % chunksPerProgressUpdate == 0 || downloaded >= fileSize) {
        int progress = (downloaded * 100) / fileSize;
        
        // 检查内存状态
        size_t currentFreeHeap = ESP.getFreeHeap();
        if (currentFreeHeap > 10000) { // 只有足够内存时才发布进度
          publishDownloadProgress(patternId, progress, "downloading");
        }
        
        Serial.printf("📊 下载进度: %d%% (%d/%d字节), 可用内存: %zu\n", 
                     progress, downloaded, fileSize, currentFreeHeap);
      }
      
      // 短暂延迟避免网络过载
      delay(10);
      
    } else {
      Serial.printf("❌ 分片下载失败，HTTP错误码: %d\n", httpCode);
      http.end();
      
      // 简单重试一次
      delay(1000);
      continue;
    }
  }
  
  Serial.printf("✅ 分片下载完成: %d/%d 字节\n", downloaded, fileSize);
  return downloaded == fileSize;
}

// 带文件名参数的重载函数
bool MQTTManager::downloadTHRFileWithName(const String& url, const String& patternId, const String& fileName, const String& checksum) {
  Serial.printf("🌐 开始分片下载THR文件: %s -> %s\n", url.c_str(), fileName.c_str());
  
  // 分片下载配置
  const size_t CHUNK_SIZE = 4096; // 4KB per chunk - 更小的分片大小
  const int MAX_RETRIES = 3;
  const int CHUNKS_PER_PROGRESS_UPDATE = 5; // 每5个分片更新一次进度
  
  Serial.println("🔧 开始内存优化模式...");
  
  // 1. 临时暂停非关键功能
  bool mqttWasConnected = isConnected();
  if (mqttWasConnected) {
    Serial.println("⏸️ 临时断开MQTT连接以释放内存");
    disconnect();
    delay(100);
  }
  
  // 2. 强制垃圾回收
  ESP.getHeapSize(); // 触发内存整理
  yield();
  delay(200);
  
  // 检查内存可用性
  if (!checkMemoryAvailability()) {
    Serial.println("❌ 内存不足，无法下载文件");
    // 恢复MQTT连接
    if (mqttWasConnected) {
      connect();
    }
    return false;
  }
  
  // 检查URL有效性
  if (url.length() == 0 || patternId.length() == 0 || fileName.length() == 0) {
    Serial.println("❌ 下载参数无效");
    return false;
  }
  
  // 强制内存清理
  ESP.getHeapSize(); // 触发内存整理
  yield();
  delay(100);
  
  // 检查可用内存 - 分片下载只需要很少内存
  size_t freeHeap = ESP.getFreeHeap();
  Serial.printf("🔍 当前可用内存: %zu 字节\n", freeHeap);
  
  // 分片下载只需要8KB内存即可
  if (freeHeap < 8000) {
    Serial.printf("⚠️ 内存不足，无法下载: 可用=%zu, 需要=8000\n", freeHeap);
    publishDownloadProgress(patternId, 0, "failed");
    
    // 恢复MQTT连接
    if (mqttWasConnected && !isConnected()) {
      Serial.println("🔄 恢复MQTT连接...");
      delay(1000);
      connect();
    }
    return false;
  }
  
  // 1. 首先获取文件大小
  int fileSize = getFileSize(url);
  if (fileSize <= 0) {
    Serial.println("❌ 无法获取文件大小");
    publishDownloadProgress(patternId, 0, "failed");
    
    // 恢复MQTT连接
    if (mqttWasConnected && !isConnected()) {
      Serial.println("🔄 恢复MQTT连接...");
      delay(1000);
      connect();
    }
    return false;
  }
  
  Serial.printf("📦 文件大小: %d 字节，分片大小: %d 字节\n", fileSize, CHUNK_SIZE);
  
  // 2. 创建patterns目录
  if (!SD.exists("/patterns")) {
    SD.mkdir("/patterns");
    Serial.println("📁 创建patterns目录");
  }
  
  // 3. 使用提供的文件名创建目标文件
  String filePath = "/patterns/" + fileName;
  File file = SD.open(filePath, FILE_WRITE);
  if (!file) {
    Serial.printf("❌ 无法创建文件: %s\n", filePath.c_str());
    publishDownloadProgress(patternId, 0, "failed");
    
    // 恢复MQTT连接
    if (mqttWasConnected && !isConnected()) {
      Serial.println("🔄 恢复MQTT连接...");
      delay(1000);
      connect();
    }
    return false;
  }
  
  Serial.printf("📝 创建目标文件: %s\n", filePath.c_str());
  
  // 4. 使用现有的分片下载函数
  bool downloadSuccess = downloadInChunks(url, file, fileSize, patternId, CHUNK_SIZE, CHUNKS_PER_PROGRESS_UPDATE);
  
  file.close();
  
  if (!downloadSuccess) {
    Serial.printf("❌ 分片下载失败\n");
    SD.remove(filePath);
    publishDownloadProgress(patternId, 0, "failed");
    
    // 恢复MQTT连接
    if (mqttWasConnected && !isConnected()) {
      Serial.println("🔄 恢复MQTT连接...");
      delay(1000);
      connect();
    }
    return false;
  }
  
  // 发布最终进度
  publishDownloadProgress(patternId, 100, "completed");
  
  // 恢复MQTT连接
  if (mqttWasConnected && !isConnected()) {
    Serial.println("🔄 恢复MQTT连接...");
    delay(1000);
    connect();
  }
  
  Serial.printf("✅ 分片下载完成: %s\n", fileName.c_str());
  return downloadSuccess;
}
